package yawning.mew.character;

import yawning.mew.MewProperty;
import yawning.mew.ProcEffect.Effect;
import yawning.mew.sim.SimulationEngine;
import yawning.mew.sim.SimulationProc.EffectCallback;
import yawning.mew.sim.SimulationTarget;
import yawning.mew.target.Target;

public class CharacterModel {
	protected static final MewProperty BUG_POOLED_PROC_EFFECTS = new MewProperty("Bug.PooledProcEffects", false);	// Munching/Rolling/Refresh Bug.

	public final Toon mToon;
	public final StatLogic mStatLogic;
	protected Target mTarget;

	// Proc effects.
	protected double bonusAgility = 0.0;
	protected double bonusStrength = 0.0;
	protected double bonusStamina = 0.0;
	protected double bonusIntellect = 0.0;
	protected double bonusSpirit = 0.0;
	protected double bonusAttackPower = 0.0;
	protected double bonusCritRating = 0.0;
	protected double bonusHasteRating = 0.0;
	protected double bonusMasteryRating = 0.0;
	protected double bonusHitRating = 0.0;
	protected double bonusExpertise = 0.0;
	protected double bonusExpertiseRating = 0.0;
	protected double bonusWeaponDamage = 0.0;
	protected double bonusDodge = 0.0;
	protected double bonusDodgeRating = 0.0;
	protected double bonusParry = 0.0;
	protected double bonusParryRating = 0.0;
	protected double bonusBlock = 0.0;
	protected double bonusMissChance = 0.0;
	protected double bonusHitChance = 0.0;
	protected double bonusArmor = 0.0;

	protected double bonusMeleeCrit = 0.0;
	protected double bonusSpellCrit = 0.0;
	protected double bonusHealCrit = 0.0;
	protected double bonusMobCrit = 0.0;

	protected double strMult = 1.0;
	protected double agiMult = 1.0;
	protected double staMult = 1.0;
	protected double intMult = 1.0;
	protected double spiMult = 1.0;
	protected double apMult = 1.0;
	protected double hasteMult = 1.0;
	protected double meleeHasteMult = 1.0;
	protected double spellHasteMult = 1.0;
	protected double maxHPMult = 1.0;

	protected double physicalCritMult = 2.0;
	protected double spellCritMult = 1.5;
	protected double healCritMult = 2.0;

	// Armor
	private double armorMult = 1.0;
	private double baseArmorMult = 1.0;

	// Damage reduction
	private double incPhysicalDamageMult = 1.0;
	private double incMagicDamageMult = 1.0;

	// Uptime based buffs.
	public final double debuffShatteringThrow = 0.8;
	public final double buffHeroism = 1.3;
	public final double buffUnholyFrenzy = 1.2;
	public final double buffToTT = 1.15;

	public final int tottBuffDuration = 6;
	public final int tottCDDuration = 30;
	public final int heroismBuffDuration = 40;
	public final int heroismCDDuration = 600;
	public final int unholyFrenzyBuffDuration = 30;
	public final int unholyFrenzyCDDuration = 180;
	public final int shatteringThrowDebuffDuration = 10;
	public final int shatteringThrowCDDuration = 300;

	private int mBaseHealth;
	private int mBaseStrength;

	private boolean mCanBlock = false;
	private boolean mCanParry = false;

	protected CharacterModel(Toon t) {
		mToon = t;
		mStatLogic = StatLogic.get(t);
		mBaseHealth = mStatLogic.getBaseHealth();
		mBaseStrength = mStatLogic.getBaseStrength(mToon.mRace);

		// Racials
		if (mToon.mRace == Race.TAUREN) mBaseHealth *= 1.05;
		if (mToon.mRace == Race.NIGHT_ELF) bonusMissChance += 0.02;
		if (mToon.mRace == Race.WORGEN) bonusMeleeCrit += 0.01;
		if (mToon.mRace == Race.DRAENEI) bonusHitChance += 0.01;
		if (mToon.mRace == Race.GOBLIN) hasteMult *= 1.01;
		if (mToon.isWeaponRacial) {
			switch(mToon.mRace) {
			case DWARF: bonusExpertise += 3; break;
			case GNOME: bonusExpertise += 3; break;
			case HUMAN: bonusExpertise += 3; break;
			case ORC: bonusExpertise += 5; break;
			}
		}

		// Meta Gems
		if (mToon.mMetaGem == MetaGem.AUSTERE) armorMult *= 1.02;
		if (mToon.mMetaGem == MetaGem.AGILE || mToon.mMetaGem == MetaGem.RELENTLESS || mToon.mMetaGem == MetaGem.REVERBERATING) {
			physicalCritMult *= 1.03;
			spellCritMult *= 1.03;
		}
		if (mToon.mMetaGem == MetaGem.EFFULGENT) incMagicDamageMult *= (1 - 0.02);
//		if (mToon.mMetaGem == MetaGem.REVITALIZING) healCritMult *= 1.03;	// XXX: Eventually some retard will ask for this.

		// Final Class setup
		if (mToon.mClass == Clazz.WARRIOR) {
			mCanBlock = true;
			mCanParry = true;
		}
	}

	public void setTarget(Target target) {
		mTarget = target;

		mMeleeCritChanceCacheValid = false;
		mMeleeMissedChanceCacheValid = false;
		mMeleeDodgedChanceCacheValid = false;
	}

	public Target getTarget() {
		return mTarget;
	}

	public void addAgility(double agi) {
		bonusAgility += agi;
		mMeleeCritChanceCacheValid = false;
	}

	public void addStrength(double str) {
		bonusStrength += str;
	}

	public void addStamina(double sta) {
		bonusStamina += sta;
	}

	public void addIntellect(double intellect) {
		bonusIntellect += intellect;
	}

	public void addSpirit(double spi) {
		bonusSpirit += spi;
	}

	public void addArmor(double armor) {
		bonusArmor += armor;
	}

	public void addAttackPower(double ap) {
		bonusAttackPower += ap;
	}

	public void addCritRating(double crit) {
		bonusCritRating += crit;
		mMeleeCritChanceCacheValid = false;
	}

	public double getCritRating() {
		return mToon.critRating + bonusCritRating;
	}

	public void addHasteRating(double haste) {
		mHasteCacheValid = false;
		mMeleeIntervalCacheValid = false;
		bonusHasteRating += haste;
	}

	public double getHasteRating() {
		return mToon.hasteRating + bonusHasteRating;
	}

	public void addMasteryRating(double mr) {
		bonusMasteryRating += mr;
	}

	public double getMasteryRating() {
		return mToon.masteryRating + bonusMasteryRating;
	}

	public void addHitRating(double hit) {
		mMeleeMissedChanceCacheValid = false;
		bonusHitRating += hit;
	}

	public void addExpertiseRating(double exp) {
		mMeleeDodgedChanceCacheValid = false;
		bonusExpertiseRating += exp;
	}

	public void addWeaponDamage(double wd) {
		bonusWeaponDamage += wd;
	}

	public void addMeleeCrit(double crit) {
		bonusMeleeCrit += crit;
		mMeleeCritChanceCacheValid = false;
	}

	public void addSpellCrit(double crit) {
		bonusSpellCrit += crit;
	}

	public void addHealCrit(double crit) {
		bonusHealCrit += crit;
	}

	public void addMobCrit(double crit) {
		bonusMobCrit += crit;
	}

	public void addDodge(double dodge) {
		bonusDodge += dodge;
	}

	public void addDodgeRating(double dodgeRating) {
		bonusDodgeRating += dodgeRating;
	}

	public void addParryRating(double parryRating) {
		bonusParryRating += parryRating;
	}

	public void addBlock(double block) {
		bonusBlock += block;
	}

	public void addParry(double parry) {
		bonusParry += parry;
	}

	public void multiplyAgility(double m) {
		agiMult *= m;
		mMeleeCritChanceCacheValid = false;
	}

	public void multiplyStrength(double m) {
		strMult *= m;
	}

	public void multiplyStamina(double m) {
		staMult *= m;
	}

	public void multiplyIntellect(double m) {
		intMult *= m;
	}

	public void multiplySpirit(double m) {
		spiMult *= m;
	}

	public void multiplyAttackPower(double m) {
		apMult *= m;
	}

	public void multiplyHaste(double m) {
		mHasteCacheValid = false;
		mMeleeIntervalCacheValid = false;
		hasteMult *= m;
	}

	public void multiplyMeleeHaste(double m) {
		mMeleeIntervalCacheValid = false;
		meleeHasteMult *= m;
	}

	public void multiplySpellHaste(double m) {
		spellHasteMult *= m;
	}

	public void multiplyMaxHP(double m) {
		maxHPMult *= m;
	}

	public void multiplyArmor(double m) {
		armorMult *= m;
	}

	public void multiplyBaseArmor(double m) {
		baseArmorMult *= m;
	}

	public void multiplyIncPhysicalDamage(double m) {
		incPhysicalDamageMult *= m;
	}

	public void multiplyIncMagicDamage(double m) {
		incMagicDamageMult *= m;
	}

	public double getMissChance() {
		return Math.min(Math.max(0, mStatLogic.getPBaseMiss() + bonusMissChance - mTarget.getAvoidanceDepression()), 1.0);
	}

	private double mMeleeMissedChanceCache;
	private boolean mMeleeMissedChanceCacheValid = false;
	public double getMissedChance() {
		if (!mMeleeMissedChanceCacheValid) {
			double plusHit = (mToon.hitRating + bonusHitRating) / mStatLogic.getHitConv() / 100;
			mMeleeMissedChanceCache =  Math.max(mTarget.getBaseMissRate() - (plusHit + bonusHitChance), 0);
			mMeleeMissedChanceCacheValid = true;
		}
		return mMeleeMissedChanceCache;
	}

	public double getDodgeChance() {
		double agiDodgeConv = mStatLogic.getAgiDodgeConv();
		double baseAgiDodge = 0.0f, diminishingReturnDodge = 0.0f;
		if (agiDodgeConv > 0) {	// All the cool kids get dodge from agi.
			baseAgiDodge = mStatLogic.getBaseAgility(mToon.mRace) / agiDodgeConv / 100;
			diminishingReturnDodge = getAgility() / agiDodgeConv - baseAgiDodge * 100;
		}
		diminishingReturnDodge += (mToon.mDodgeRating + bonusDodgeRating) / mStatLogic.getDodgeConv();
		double noDiminishingReturnDodge = baseAgiDodge + bonusDodge + mStatLogic.getBaseDodge();
		double postDiminishingReturnDodge = (1 / ( 1 / mStatLogic.getDodgeCap() + mStatLogic.getDiminishingReturnConstant() / diminishingReturnDodge));
		return Math.min(Math.max(0, noDiminishingReturnDodge + (postDiminishingReturnDodge / 100) - mTarget.getAvoidanceDepression()), 1.0);
	}

	private double mMeleeDodgedChanceCache;
	private boolean mMeleeDodgedChanceCacheValid = false;
	public double getDodgedChance() {
		if (!mMeleeDodgedChanceCacheValid) {
			double minusDodge = (mToon.expertiseRating + bonusExpertiseRating) / mStatLogic.getExpConv() / 100;
			minusDodge += bonusExpertise / 4 / 100;
			mMeleeDodgedChanceCache = Math.max(mTarget.getBaseDodgeRate() - minusDodge, 0);
			mMeleeDodgedChanceCacheValid = true;
		}
		return mMeleeDodgedChanceCache;
	}

	public double getParryChance() {
		if (mCanParry == false) return 0.0;

		// Note: The Armory/in game tooltips include str parry in parry rating, but I assume that it is separate.
		double strParryConv = mStatLogic.getStrParryConv();
		double diminishingReturnParry = 0.0f;
		if (strParryConv > 0) {
			diminishingReturnParry = Math.floor((getStrength() - mBaseStrength) * strParryConv) / mStatLogic.getParryConv();
		}
		diminishingReturnParry += (mToon.mParryRating + bonusParryRating) / mStatLogic.getParryConv();
		double noDiminishingReturnParry = mStatLogic.getBaseParry() + bonusParry;
		double postDiminishingReturnParry = (1 / (1 / mStatLogic.getParryCap() + mStatLogic.getDiminishingReturnConstant() / diminishingReturnParry));

		return Math.min(Math.max(0, noDiminishingReturnParry + (postDiminishingReturnParry / 100) - mTarget.getAvoidanceDepression()), 1.0);
	}

	public double getParriedChance() {
		double minusParry = (mToon.expertiseRating + bonusExpertiseRating) / mStatLogic.getExpConv() / 100;
		minusParry += bonusExpertise / 4 / 100;
		return Math.max(mTarget.getBaseParryRate() - minusParry, 0);
	}

	public double getBlockChance() {
		if (mCanBlock == false) return 0.0;

		// Classes with mastery that affect block chance should override this, especially since
		// one of the classes that has it cares about values past "filling up the combat table"
		// due to conversion to crit block.
		double baseBlock = mStatLogic.getBaseBlock() - mTarget.getAvoidanceDepression();

		return Math.min(Math.max(0, baseBlock + bonusBlock), 1.0);
	}

	public double getSpellMissChance() {
		double plusSpellHit = (mToon.hitRating + bonusHitRating) / mStatLogic.getSpellHitConv() / 100 + bonusHitChance;
		return plusSpellHit > mTarget.getBaseSpellMissRate() ? 0 : mTarget.getBaseSpellMissRate() - plusSpellHit;
	}

	public double getSpellCritChance() {
		double pIntCrit = (mStatLogic.getIntCritConv() > 0) ? getIntellect() / mStatLogic.getIntCritConv() / 100 : 0;
		double pCritRCrit = (mToon.critRating + bonusCritRating) / mStatLogic.getCritConv() / 100;
		double pCritSuppression = mTarget.getSpellCritDepression();
		double pRawCrit = bonusSpellCrit + pIntCrit + pCritRCrit;
		return Math.max(Math.min(pRawCrit - pCritSuppression, 1.0), 0);
	}

	public double getMobCritChance() {
		return Math.max(Math.min(mTarget.getCritRate() + bonusMobCrit, 1.0), 0);
	}

	public double getHealCritChance() {
		double pIntCrit = (mStatLogic.getIntCritConv() > 0) ? getIntellect() / mStatLogic.getIntCritConv() / 100 : 0;
		double pCritRCrit = (mToon.critRating + bonusCritRating) / mStatLogic.getCritConv() / 100;
		double pRawCrit = bonusHealCrit + pIntCrit + pCritRCrit;
		return Math.min(pRawCrit, 1.0);
	}

	private double mMeleeCritChanceCache;
	private boolean mMeleeCritChanceCacheValid = false;
	public double getMeleeCritChance() {
		if (!mMeleeCritChanceCacheValid) {
			double pAgiCrit = getAgility() / mStatLogic.getAgiCritConv() / 100;
			double pCritRCrit = (mToon.critRating + bonusCritRating) / mStatLogic.getCritConv() / 100;
			double pCritSuppression = mTarget.getMeleeCritDepression();
			double pRawCrit = bonusMeleeCrit + pAgiCrit + pCritRCrit;
			mMeleeCritChanceCache = Math.max(Math.min(pRawCrit - pCritSuppression, 1.0), 0);
			mMeleeCritChanceCacheValid = true;
		}
		return mMeleeCritChanceCache;
	}

	private double mHasteCache;
	private boolean mHasteCacheValid = false;
	public double getHaste() {
		if (!mHasteCacheValid) {
			double hasteRating = mToon.hasteRating + bonusHasteRating;
			double hasteRHaste = 1 + (hasteRating / mStatLogic.getHasteConv() / 100);
			mHasteCache = hasteRHaste * hasteMult;
			mHasteCacheValid = true;
		}
		return mHasteCache;
	}

	public double getSpellHaste() {
		return getHaste() * spellHasteMult;
	}

	private double mMeleeIntervalCache;
	private boolean mMeleeIntervalCacheValid = false;
	public double getMeleeInterval() {
		if (!mMeleeIntervalCacheValid) {
			mMeleeIntervalCache =  getBaseWeaponSpeed() / getHaste() / meleeHasteMult;
			mMeleeIntervalCacheValid = true;
		}
		return mMeleeIntervalCache;
	}

	public double getBaseWeaponSpeed() {
		return (double) mToon.weaponSpeed / 1000;
	}

	public double getMastery() {
		double masteryRMast = (mToon.masteryRating + bonusMasteryRating) / mStatLogic.getMasteryConv();
		return mStatLogic.getBaseMastery() + masteryRMast;
	}

	public double getAttackPower() {
		return getStrength() - 10 + mToon.attackPower + bonusAttackPower;
	}

	public double getAgility() {
		return (mToon.agility + bonusAgility) * agiMult;
	}

	public double getStrength() {
		return (mToon.strength + bonusStrength) * strMult;
	}

	public double getStamina() {
		return (mToon.stamina + bonusStamina) * staMult;
	}

	public double getIntellect() {
		return (mToon.intellect + bonusIntellect) * intMult;
	}

	public double getSpirit() {
		return (mToon.spirit + bonusSpirit) * spiMult;
	}

	public double getHealth() {
		return ((getStamina() - 20) * 14 + mBaseHealth + 20) * maxHPMult;
	}

	public double getVengeanceCap() {
		return getStamina() + mBaseHealth / 10;
	}

	public double getArmor() {
		return (mToon.armor * baseArmorMult) * armorMult + mToon.bonusArmor + bonusArmor;
	}

	public double getWeaponDamage() {
		return 1 + (double)(mToon.weaponTop + mToon.weaponBottom) / 2  + bonusWeaponDamage + getAttackPower() * mToon.weaponSpeed / 14000;
	}

	public double getWhiteCritChance() {
		return Math.min(getMeleeCritChance(), 1 - (getDodgedChance() + getMissedChance() + mTarget.getGlanceRate()));
	}

	public double getWhiteCritCap() {
		return 1 - getDodgedChance() - getMissedChance() - mTarget.getGlanceRate();
	}

	public double getIncPhysicalDamageMult() {
		return incPhysicalDamageMult;
	}

	public double getIncMagicDamageMult() {
		return incMagicDamageMult;
	}

	public final static double calculateArmorDR(double armor, int level) {
		double multTargetArmorDR;

		// Detailed formulas from FrameXML/PaperDollFrame.lua (Thanks Blizzard!)
		// Level 1-59: DR% = Armor / (Armor + 400 + 85 * AttackLevel)
		// Level 60-80: DR % = Armor / (Armor + 400 + 85 * (AttackerLevel + 4.5 * (AttackerLevel - 59)))
		// Level 81-85: DR % = Armor / (Armor + 400 + 85 * (AttackerLevel + 4.5 * (AttackerLevel - 59) + 20 * (AttackerLevel - 80)))
		if (level < 60) {
			multTargetArmorDR = armor / (armor + 400 + 85 * level);
		} else if (level <= 80) {
			multTargetArmorDR = armor / (armor + 400 + 85 * (level + 4.5 * (level - 59)));
		} else {
			multTargetArmorDR = armor / (armor + 400 + 85 * (level + 4.5 * (level - 59) + 20 * (level - 80)));
		}

		multTargetArmorDR = Math.min(multTargetArmorDR, 0.75);

		return multTargetArmorDR;
	}

	// Simulation specific utility functions
	private SimulationEngine mSimUtil;

	public void setSimulationEngine(SimulationEngine se) {
		mSimUtil = se;

		mSimUtil.registerProcEffectCallback(Effect.AGILITY, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addAgility(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.ATTACK_POWER, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addAttackPower(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.CRIT_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addCritRating(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.EXPERTISE_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addExpertiseRating(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.HASTE_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addHasteRating(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.HIT_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addHitRating(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.MASTERY_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addMasteryRating(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.WEAPON_DAMAGE, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addWeaponDamage(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.DODGE_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addDodgeRating(n);
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.PARRY_RATING, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addParryRating(n);
			}
		});

		// TODO:
//		mSimUtil.registerProcEffectCallback(Effect.PHYSICAL_DAMAGE, new EffectCallback() {
//			@Override
//			public void onEffect(SimulationTarget target, int n) {
//				AttackResult result = getYellowResult();
//				double damage = n;
//
//				if (result != AttackResult.MISS && result != AttackResult.DODGE) {
//					if (result == AttackResult.CRIT) {
//						damage *= multCrit;
//					}
//
//					damage *= getDirectDamageMult();
//					procPhysDamage += Math.round(damage);
//				}
//			}
//		});
//		mSimUtil.registerProcEffectCallback(Effect.SPELL_DAMAGE, new EffectCallback() {
//			@Override
//			public void onEffect(SimulationTarget target, int n) {
//				AttackResult result = getSpellResult();
//				double damage = n;
//
//				if (result != AttackResult.MISS) {
//					if (result == AttackResult.CRIT) {
//						damage *= multSpellCrit;
//					}
//
//					// XXX: Factor in Spell Vulnerability here.
//					procSpellDamage += Math.round(damage);
//				}
//			}
//		});
		mSimUtil.registerProcEffectCallback(Effect.STRENGTH, new EffectCallback() {
			@Override
			public void onEffect(SimulationTarget target, int n) {
				addStrength(n);
			}
		});
	}

	public enum AttackResult { MISS, DODGE, PARRY, GLANCE, BLOCK, CRIT, HIT };

	public AttackResult getWhiteResult() {
		SimulationTarget target = mSimUtil.getActiveTarget();

		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissedChance();
		double pDodge;
		if (target.getTargetStunEv().isUp() || target.getTarget().getIsPlayer()) {
			pDodge = 0.0f;
		} else {
			pDodge = getDodgedChance();
		}
		double pGlance = mTarget.getGlanceRate();
		double pWhiteCrit = Math.min(getMeleeCritChance(), 1 - (pDodge + pMiss + pGlance));

		// White swings are one roll.
		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pGlance) < 0) {
			return AttackResult.GLANCE;
		} else if ((roll -= pWhiteCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getFrontalWhiteResult() {
		SimulationTarget target = mSimUtil.getActiveTarget();

		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissedChance();
		double pDodge, pParry;
		if (target.getTargetStunEv().isUp() || target.getTarget().getIsPlayer()) {
			pDodge = 0.0f;
			pParry = 0.0f;
		} else {
			pDodge = getDodgedChance();
			pParry = getParriedChance();
		}
		double pGlance = mTarget.getGlanceRate();
		double pWhiteCrit = Math.min(getMeleeCritChance(), 1 - (pDodge + pMiss + pParry + pGlance));

		// XXX: Do mobs still block?

		// White swings are one roll.
		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pParry) < 0) {
			return AttackResult.PARRY;
		} else if ((roll -= pGlance) < 0) {
			return AttackResult.GLANCE;
		} else if ((roll -= pWhiteCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getYellowResult() {
		SimulationTarget target = mSimUtil.getActiveTarget();

		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissedChance();
		double pDodge;
		if (target.getTargetStunEv().isUp() || target.getTarget().getIsPlayer()) {
			pDodge = 0.0f;
		} else {
			pDodge = getDodgedChance();
		}
		double pCrit = getMeleeCritChance() * (1.0 - (pMiss + pDodge));		// Yellow swings are two roll.

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getYellowResult(double pBonusCrit) {
		SimulationTarget target = mSimUtil.getActiveTarget();

		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss, pDodge;
		if (target.getTargetStunEv().isUp() || target.getTarget().getIsPlayer()) {
			pMiss = 0.0f;
			pDodge = 0.0f;
		} else {
			pMiss = getMissedChance();
			pDodge = getDodgedChance();
		}
		double pCrit = Math.min(1, getMeleeCritChance() + pBonusCrit) * (1.0 - (pMiss + pDodge));

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getFrontalYellowResult() {
		SimulationTarget target = mSimUtil.getActiveTarget();

		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissedChance();
		double pDodge, pParry;
		if (target.getTargetStunEv().isUp() || target.getTarget().getIsPlayer()) {
			pDodge = 0.0f;
			pParry = 0.0f;
		} else {
			pDodge = getDodgedChance();
			pParry = getParriedChance();
		}
		double pCrit = getMeleeCritChance() * (1.0 - (pMiss + pDodge + pParry));	// Yellow swings are two roll.

		// XXX: Do mobs still block?

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pParry) < 0) {
			return AttackResult.PARRY;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getFrontalYellowResult(double pBonusCrit) {
		SimulationTarget target = mSimUtil.getActiveTarget();

		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissedChance();
		double pDodge, pParry;
		if (target.getTargetStunEv().isUp() || target.getTarget().getIsPlayer()) {
			pDodge = 0.0f;
			pParry = 0.0f;
		} else {
			pDodge = getDodgedChance();
			pParry = getParriedChance();
		}
		double pCrit = Math.min(1, getMeleeCritChance() + pBonusCrit) * (1.0 - (pMiss + pDodge + pParry));	// Yellow swings are two roll.

		// XXX: Do mobs still block?

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pParry) < 0) {
			return AttackResult.PARRY;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getYellowStealthResult(double pBonusCrit) {
		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissedChance();
		double pCrit = Math.min(1, getMeleeCritChance() + pBonusCrit) * (1.0 - pMiss);

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getDotResult(double pCrit) {
		boolean roll = mSimUtil.getRandom().nextBoolean(pCrit);
		return (roll) ? AttackResult.CRIT : AttackResult.HIT;
	}

	public AttackResult getSpellResult() {
		double roll = mSimUtil.getRandom().nextDouble();
		double pSpellMiss = getSpellMissChance();
		double pSpellCrit = getSpellCritChance();

		if ((roll -= pSpellMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pSpellCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getMobWhiteResult() {
		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissChance();
		double pDodge = getDodgeChance();
		double pParry = getParryChance();
		double pBlock = Math.min(1, getBlockChance());
		double pCrit = Math.min(1, getMobCritChance());

		// White swings are one roll.
		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pParry) < 0) {
			return AttackResult.PARRY;
		} else if ((roll -= pBlock) < 0) {
			return AttackResult.BLOCK;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getMobMagicResult() {
		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissChance();

		// Mob specials by definition can't crit.
		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		}

		return AttackResult.HIT;
	}

	public AttackResult getMobYellowResult() {
		double roll = mSimUtil.getRandom().nextDouble();
		double pMiss = getMissChance();
		double pDodge = getDodgeChance();
		double pParry = getParryChance();
		double pBlock = Math.min(1, getBlockChance());

		// Mob specials by definition can't crit.
		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pParry) < 0) {
			return AttackResult.PARRY;
		} else if ((roll -= pBlock) < 0) {
			return AttackResult.BLOCK;
		}

		return AttackResult.HIT;
	}

	// Formulation specific utility functions.

    /**
     * Calculate cooldown uptime, assuming it is used at the earliest possible opportunity.
     *
     * @return Uptime in seconds.
     */
    public final double GetCooldownUptime(double up, int down, int total) {
        int fullUses = total/down;
        int timeLeft = total - fullUses * down;
        double partialUp = (timeLeft >= up) ? up : timeLeft;

        return up * fullUses + partialUp;
    }

    /**
     * Calculate proc uptime %.
     *
     * @return % uptime.
     */
    public final double GetProcUptime(double procRate, double numTriggers, double buffDuration, double icd) {
        double uptime = 0;

        if (icd > 0) {
            // Uptime = Duration / (ICD + 1 / (Triggers * Proc Rate))
            uptime = buffDuration / (icd + 1 / (numTriggers * procRate));
        } else {
            // Uptime = 1 - (1 - Proc Rate) ^ (Duration * Triggers)
            uptime = 1 - Math.pow((1 - procRate),(buffDuration * numTriggers));
        }

        return uptime;
    }
}
